package com.jule.robot.service.websocket;

import JoloProtobuf.AuthSvr.JoloAuth;
import JoloProtobuf.GameSvr.JoloGame;
import JoloProtobuf.RoomSvr.JoloRoom;
import com.jule.core.jedis.BinaryJedis.BinaryJedisString;
import com.jule.core.jedis.StoredObjManager;
import com.jule.db.entities.RobotHandCardRateConfigModel;
import com.jule.db.entities.RoomConfigModel;
import com.jule.db.entities.User;
import com.jule.db.proxy.EntityProxy;
import com.jule.robot.config.Config;
import com.jule.robot.model.BlindActionRateModel;
import com.jule.robot.model.CardValueModel;
import com.jule.robot.model.HandCardTypeModel;
import com.jule.robot.model.PlayerInfo;
import com.jule.robot.model.eenum.PlayTypeEnum;
import com.jule.robot.model.eenum.PlayerStateEnum;
import com.jule.robot.service.holder.*;
import com.jule.robot.strategy.BaseRobotStrategry;
import com.jule.robot.strategy.StrategryReflectMap;
import com.jule.robot.util.RandomTools;
import com.jule.robot.valve.gate.RobotConfigHandler;
import com.jule.teenpatti.base.enums.RedisConst;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;

public class RobotGameWebSocketClient extends BaseWebSocketClient {
    private final static Logger logger = LoggerFactory.getLogger(RobotGameWebSocketClient.class);
    private int alreadyPlayRound = 0;
    private final int randomRound = RandomTools.getRandomNum(17)+3; //3-20局之间
    private String verify = ""; //登录证书
    private long _baseBetScore = 0;
    private int isCanShow = 0;
    private int isCanSideShow = 0;
    private String currPlayType = ""; //Dealer玩法时的当前选择的玩法
    private boolean isNeedFoldBy91001005 = false; //该机器人是否需要在Blind玩法时弃牌

    public int getIsCanShow() {
        return isCanShow;
    }

    public int getIsCanSideShow() {
        return isCanSideShow;
    }

    @Getter
    private AuthWebsocketClient authWebsocketClient;

    public RobotGameWebSocketClient(int gameId, String userId, String roomId, String tableId, String verify, AuthWebsocketClient authWebsocketClient) {
//        logger.warn("new RobotGameWebSocketClient(), gameId->{}, userId->{}, roomId->{}, tableId->{}, clientObject->{}", gameId, userId, roomId, tableId, this.toString());
        this.gameId = gameId;
        this.roomId = roomId;
        this.tableId = tableId;
        this.userId = userId;
        this.verify = verify;
        this.authWebsocketClient = authWebsocketClient;
        super.connectWebSocket(FunctionIdHolder.GATE_SVR_URI);
    }

//    public RobotGameWebSocketClient(String webSocketURI) {
//        super(webSocketURI);
//    }

    @Override
    public void routeFunctionId(int functionId, byte[] bytes) {
        BaseRobotStrategry strategy = StrategryReflectMap.getStrategry(functionId);
        switch (functionId) {
            //Room
            case FunctionIdHolder.Room_ACK_ApplyJoinTable:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.GATE_ACK_loginUser:
                AckLoginUser(functionId, bytes);
                return;

            //Game
            case FunctionIdHolder.Game_ACK_ApplySitDown:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplyStandUp:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplyLeave:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplyBuyIn:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplyBet:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplySeeCard:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplySideShow:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplyShow:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_GetSideShowState:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_IsAllowSideShow:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplyChooseUser:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_ApplyFold:
                strategy.doAction(this, functionId, bytes);
                return;
            case FunctionIdHolder.Game_ACK_GiftsList:
                strategy.doAction(this, functionId, bytes);
                return;

            //NOTICE
            case FunctionIdHolder.Game_Notice_SiteDown:
                NoticeSitDown(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_StandUp:
                NoticeStandUp(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_BuyIn:
                NoticeBuyIn(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_GameStart:
                NoticeGameStart(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_GiveCardRound_Start:
                NoticeGiveCardRoundStart(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_BetRound_DoBet:
                NoticeBetRoundDoBet(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_PlayerSeeCard:
                NoticePlayerSeeCard(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SideShow_ApplySideShow:
                NoticeApplySideShow(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SideShow_WaitTargetUserAllow:
                NoticeWaitUserAllow(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SideShow_WaitChooseUser:
                NoticeWaitChooseUser(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SideShow_SideShowResult:
                NoticeSideShowResult(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SettleRound_SettleResult:
                NoticeSettleResult(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SettleRound_History:
                NoticeSettleHistory(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SideShow_CompleteChooseUser:
                NoticeSideShowCompleteChooseUser(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_SideShow_CompleteAllow:
                NoticeSideShowCompleteAllow(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_BetRound_Fold:
                NoticeBetRoundFold(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_Settle_Prompt:
                NoticeSettlePrompt(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_MixedCard:
                NoticeMixedCard(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_ChoosePlayType:
                NoticeChoosePlayType(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_CurrPlayType:
                NoticeCurrPlayType(functionId, bytes);
                return;
            case FunctionIdHolder.Game_Notice_LeaveReq:
                NoticeLeaveReq(functionId, bytes);
                strategy.doAction(this,functionId,bytes);
                return;
            default:
                logger.debug("UNKNOW functionID ->" + functionId + ", functionName ->" + FunctionIdHolder.GetFunctionName(functionId));
        }
    }

    @Override
    public void webSocketOpen() {
        ExecuteLoginUser();
    }

    //region 执行行动
    public void ExecuteAction(String gameOrderId, long baseBetScore, int countDownSec, int betRoundId, int isCanShow, int isCanSideShow) {
        try {
            this.isCanShow = isCanShow;
            this.isCanSideShow = isCanSideShow;

            if (betRoundId == 0) {
                betRoundId = 1;
            }

            PlayerInfo playerInfo = onTablePlayers.get(userId);
            if (null == playerInfo) {
                logger.error("ExecuteAction(). 用户已站起，playerInfo为空, {}", toStringUserInfo());
                ExecuteStandUp(); //执行站起，确保机器人执行离桌操作
                return;
            }

            //机器人策略执行人
            String robotId = StoredObjManager.hget(RedisConst.ROBOT_POOL_STRATEGY_ID.getProfix()+gameOrderId, RedisConst.ROBOT_POOL_STRATEGY_ID.getField());
            logger.debug("机器人换牌策略启动or not ExcuteRobotId={},userId={}",robotId,userId);
            if (!StringUtils.isEmpty(robotId) && userId.equals(robotId)){
                logger.debug("机器人换牌策略启动userId={},nick={}",userId,playerInfo.getNickName());
                ExecuteStrategyDoBet(gameOrderId,baseBetScore,countDownSec,betRoundId,isCanShow,isCanSideShow);
                return;
            }

            //判断当前玩法是否是Blind，并且该机器人是否应当弃牌
            if(currPlayType.equals("91001005") && this.isNeedFoldBy91001005){
//                logger.warn("当前玩法是Blind，并且该机器人应执行弃牌操作。userId->{}, roomId->{}, gameId->{}", userId, roomId, gameId);
                ExecuteFold(isCanShow, isCanSideShow, true, betRoundId, baseBetScore); //强制弃牌
                return;
            }

            RoomConfigModel roomConfigModel = RoomConfigHolder.getInstance().getRoomConfig(roomId);
            if (null == roomConfigModel) {
                logger.error("ExecuteAction(). 用户已站起，房间配置为空, {}", toStringUserInfo());
                ExecuteStandUp(); //执行站起，确保机器人执行离桌操作
                return;
            }
            long limitBetScore = roomConfigModel.getMaxBetScore();

            logger.debug("机器人剩余货币， playScoreStore->{},{}, isBlind->{}", playerInfo.getPlayScoreStore(), toStringUserInfo(), null == cardModel);
            boolean isSeeCardThisTime = false;
            if (null == cardModel) {
                //判断玩家是否需要看牌
                isSeeCardThisTime = isNeedSeeCardThisRound(playerInfo, betRoundId);
            }

            int reqNum = (int) (System.currentTimeMillis() / 1000);

            logger.debug("ExecuteAction, reqNum->" + reqNum + toStringUserInfo() + ", roomId->" + roomId + ", tableId->" + tableId
                    + ", gameOrderId->" + gameOrderId + ", isBlind->" + (null == cardModel) + toStringReplaceLine()
                    + ", 牌局总人数->" + onTablePlayers.size() + ", 当前未弃牌人数->" + getUnFoldPlayerCnt());

            //如果机器人是盲牌状态，那么随机下注基础倍数或两倍
            long betScore = 0;
            logger.warn("->>输出轮次信息， nickName->{}, 轮次ID->{}, 手牌->{}, 是否发起了看牌->{}", nickName, betRoundId, null == cardModel?"盲牌":cardModel.toStringCard(), isSeeCardThisTime);
            if (null != cardModel) { //非盲牌
                if (isSeeCardThisTime) {
                    //如果是刚刚看牌，那么基础下注额需要乘2，因为game下发的下一个行动者信息中的baseBetScore，是按机器人为盲牌状态下发的。
                    baseBetScore = baseBetScore * 2;
                }
                betScore = baseBetScore;

                //获得未弃牌人数
                int unFoldPlayerCnt = getUnFoldPlayerCnt();

                /*
                 * 判断牌力值：如果轮次是第一轮，并且未弃牌人数 > 2，那么判断手牌是否小于期望值
                 */
                if(betRoundId <= 1){
                    boolean isLessThanExpectCard = isLessThanExpectCard(cardModel, unFoldPlayerCnt, nickName);
                    if(isLessThanExpectCard && unFoldPlayerCnt > 2) {
                        logger.warn("手牌小于期望牌力值，并且未弃牌人数大于2人。执行 弃牌。 nickName->{}, 未弃牌人数->{}", nickName, unFoldPlayerCnt);
                        ExecuteFold(isCanShow, isCanSideShow, true, betRoundId, baseBetScore);
                    }else{
                        ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, false);
                    }
                    return; //结束本次Action操作
                }

                /*
                 * 判断牌型是否需要根据牌力值配置表进行跟注、加注、弃牌等操作
                 */
                if(betRoundId >= 2){
                    boolean isMoreThanNeedRateCard = isMoreThanNeedConfigRateCard(cardModel, unFoldPlayerCnt, nickName);
                    if(!isMoreThanNeedRateCard){ //如果小于345
                        boolean isMoreThanAT5 = isMoreThanAT5(cardModel, nickName);
                        if(!isMoreThanAT5){
                            if(unFoldPlayerCnt > 2) {
                                logger.warn("手牌小于A 10 5，并且未弃牌人数大于2，执行 弃牌 操作。 nickName->{}, unFoldPlayerCnt", nickName, unFoldPlayerCnt);
                                ExecuteFold(0, 0, true, betRoundId, baseBetScore);
                            }else if(unFoldPlayerCnt <= 2){
                                boolean isMoreThanT93 = isMoreThanT93(cardModel, nickName);
                                if(!isMoreThanT93){
                                    //如果小于10 9 3，直接弃牌
                                    logger.warn("手牌小于10 9 3，并且小于A 10 5，执行 弃牌 操作。 nickName->{}, unFoldPlayerCnt", nickName, unFoldPlayerCnt);
                                    ExecuteFold(0, 0, true, betRoundId, baseBetScore);
                                }else{
                                    logger.warn("手牌大于10 9 3，但是小于A 10 5，执行 比牌操作。 nickName->{}", nickName);
                                    if (ExecShowOrSideShow(isCanShow, isCanSideShow, baseBetScore)) {
                                        return;
                                    }
                                }
                            }
                        }else {
                            logger.warn("手牌小于3 4 5，但是大于A 10 5，执行 比牌操作。 nickName->{}", nickName);
                            if (ExecShowOrSideShow(isCanShow, isCanSideShow, baseBetScore)) {
                                return;
                            }
                        }
                    }
                }

                long scoreVariable_A = getScoreVariable_A(baseBetScore, limitBetScore);
                logger.debug("货币量A计算，playerScoreStore->{}, 可用货币量A->{}, baseBetScore->{}, unFoldUserCnt->{}, Math.pow->{}, {}",
                        playerInfo.getPlayScoreStore(), scoreVariable_A, baseBetScore, getUnFoldPlayerCnt(), (long) Math.pow(2, getUnFoldPlayerCnt() - 1), toStringUserInfo());
                if (playerInfo.getPlayScoreStore() > scoreVariable_A) { //可用货币大于货币量A
                    ExecActionBySeedCardAndMoreThanVariableA(playerInfo, betScore, betRoundId, baseBetScore, reqNum, limitBetScore);
                } else {
                    logger.info("非盲牌：可用货币量A不满足条件。" + toStringUserInfo() + ", unFoldPlayerCnt->" + getUnFoldPlayerCnt() + ", isCanShow->" + isCanShow
                            + ", isCanSideShow->" + isCanSideShow + ", baseBetScore->" + baseBetScore);
                    if (isCanShow == 1) {
                        logger.info("行为：show牌。" + toStringUserInfo());
                        ExecuteApplyShow(); //show牌
                    } else {
                        logger.info("行为：人数过多，强制弃牌。" + toStringUserInfo());
                        ExecuteFold(isCanShow, isCanSideShow, true, betRoundId, baseBetScore); //强制弃牌
                    }
                }
            } else { //盲牌
                betScore = baseBetScore;
                BlindActionRateModel rateModel = RobotConfigHandler.getBlindActionRate(betRoundId);
                int actionRandomNum = RandomTools.getRandomNum(100);
                logger.warn("盲牌状态，判断机器人是否应下注。nickName->{}, actionRandomNum->{}, callRate->{}, betRoundId->{}", nickName, actionRandomNum, rateModel.getCallRate(), betRoundId);
                if (actionRandomNum > rateModel.getCallRate()){
                    //如果随机数，大于 下注概率，那么执行比牌操作
                    ExecShowOrSideShow(isCanShow, isCanSideShow, baseBetScore);
                }else{
                    int raiseRandomNum = RandomTools.getRandomNum(100);
                    if (raiseRandomNum <= RobotConfigHandler.getRobotCommonConfig().getRaiseRateForBlind()) { //加注
                        betScore = baseBetScore * 2;
                    }
                    logger.info("盲牌：行为：下注|" + (betScore == baseBetScore ? "跟注" : "加注") + "。" + toStringUserInfo());
                    ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, true);
                }
            }
            this._baseBetScore = baseBetScore;
        } catch (Exception ex) {
            logger.error("ExecuteBet ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //策略机器人操作
    public void ExecuteStrategyDoBet(String gameOrderId, long baseBetScore, int countDownSec, int betRoundId, int isCanShow, int isCanSideShow) {
        try {
            this.isCanShow = isCanShow;
            this.isCanSideShow = isCanSideShow;

            if (betRoundId == 0) {
                betRoundId = 1;
            }

            PlayerInfo playerInfo = onTablePlayers.get(userId);
            if (null == playerInfo) {
                logger.error("ExecuteAction(). 用户已站起，playerInfo为空, {}", toStringUserInfo());
                ExecuteStandUp(); //执行站起，确保机器人执行离桌操作
                return;
            }

            //判断当前玩法是否是Blind，并且该机器人是否应当弃牌
            if(currPlayType.equals("91001005") && this.isNeedFoldBy91001005){
                ExecuteFold(isCanShow, isCanSideShow, true, betRoundId, baseBetScore); //强制弃牌
                return;
            }

            RoomConfigModel roomConfigModel = RoomConfigHolder.getInstance().getRoomConfig(roomId);
            if (null == roomConfigModel) {
                logger.error("ExecuteAction(). 用户已站起，房间配置为空, {}", toStringUserInfo());
                ExecuteStandUp(); //执行站起，确保机器人执行离桌操作
                return;
            }
            long limitBetScore = roomConfigModel.getMaxBetScore();

            logger.debug("机器人剩余货币， playScoreStore->{},{}, isBlind->{}", playerInfo.getPlayScoreStore(), toStringUserInfo(), null == cardModel);
            boolean isSeeCardThisTime = false;
            if (null == cardModel) {
                //判断玩家是否需要看牌
                isSeeCardThisTime = isNeedSeeCardThisRound(playerInfo, betRoundId);
            }

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            //如果机器人是盲牌状态，那么随机下注基础倍数或两倍
            long betScore = 0;
            logger.warn("->>输出轮次信息， nickName->{}, 轮次ID->{}, 手牌->{}, 是否发起了看牌->{}", nickName, betRoundId, null == cardModel?"盲牌":cardModel.toStringCard(), isSeeCardThisTime);

            //盲牌
            if (cardModel == null){
                logger.debug("盲牌操作 userId={}",userId);
                betScore = baseBetScore;
                BlindActionRateModel rateModel = RobotConfigHandler.getBlindActionRate(betRoundId);
                int actionRandomNum = RandomTools.getRandomNum(100);
                logger.warn("盲牌状态，判断机器人是否应下注。nickName->{}, actionRandomNum->{}, callRate->{}, betRoundId->{}", nickName, actionRandomNum, rateModel.getCallRate(), betRoundId);
                if (actionRandomNum > rateModel.getCallRate()){
                    //如果随机数，大于 下注概率，那么执行比牌操作
                    if (!ExecShowOrSideShow(isCanShow, isCanSideShow, baseBetScore)){
                        ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, true);
                    }
                }else{
                    int raiseRandomNum = RandomTools.getRandomNum(100);
                    if (raiseRandomNum <= 40) { //加注
                        betScore = baseBetScore * 2;
                    }
                    logger.info("盲牌：行为：下注|" + (betScore == baseBetScore ? "跟注" : "加注") + "。" + toStringUserInfo());
                    ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, true);
                }

                this._baseBetScore = baseBetScore;
                return;
            }

            logger.debug("非盲牌操作 userId={}",userId);
            //非盲牌
            if (isSeeCardThisTime) {
                //如果是刚刚看牌，那么基础下注额需要乘2，因为game下发的下一个行动者信息中的baseBetScore，是按机器人为盲牌状态下发的。
                baseBetScore = baseBetScore * 2;
            }
            betScore = baseBetScore;

            //获得未弃牌人数
            int unFoldPlayerCnt = getUnFoldPlayerCnt();

            /*
             * 判断牌型是否需要根据牌力值配置表进行跟注、加注、弃牌等操作
             */
            boolean isMoreThanNeedRateCard = isMoreThanNeedConfigRateCard(cardModel, unFoldPlayerCnt, nickName);
            if(!isMoreThanNeedRateCard){
                logger.debug("非盲操作、小于345");
                //如果小于345
                int betRandomNum = RandomTools.getRandomNum(100);
                if (betRandomNum <= 50 ){
                    betScore = betScore * 2;
                }

                int actionRandomNum = RandomTools.getRandomNum(100);
                if (actionRandomNum < 70){
                    //70%跟注概率
                    logger.debug("非盲操作、小于345、ExecuteBet");
                    ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, false);
                }else {
                    logger.debug("非盲操作、小于345、ExecShowOrSideShow");
                    if (!ExecShowOrSideShow(isCanShow, isCanSideShow, baseBetScore)){
                        ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, false);
                    }
                }
            }else {
                //如果大于345
                logger.debug("非盲操作、大于345");
                int betRandomNum = RandomTools.getRandomNum(100);
                if (betRandomNum <= 70 ){
                    betScore = betScore * 2;
                }

                int actionRandomNum = RandomTools.getRandomNum(100);
                if (actionRandomNum > RobotConfigHandler.getRobotCommonConfig().getSeeCardRate()){
                    //跟注概率
                    logger.debug("非盲操作、大于345、ExecuteBet");
                    ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, false);
                }else {
                    logger.debug("非盲操作、大于345、ExecShowOrSideShow");
                    if (!ExecShowOrSideShow(isCanShow, isCanSideShow, baseBetScore)){
                        ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, false);
                    }
                }
            }
            this._baseBetScore = baseBetScore;
        } catch (Exception ex) {
            logger.error("ExecuteStrategyDoBet ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    /**
     * 是否需要在本轮次看牌
     * @param playerInfo
     * @param betRoundId
     * @return
     */
    private boolean isNeedSeeCardThisRound(PlayerInfo playerInfo, int betRoundId){
        boolean isSeeCardThisTime = false;
        long moneyToSee = RoomConfigHolder.getInstance().getRoomConfig(roomId).getAnte() * RobotConfigHandler.getRobotCommonConfig().getMoneyStoreToSee();
        if (playerInfo.getPlayScoreStore() <= moneyToSee) {
            logger.info("执行Action前，货币余额已达到看牌标准。{}, playScoreStore->" + playerInfo.getPlayScoreStore() + ", MoneyStoreToSee->" + moneyToSee, toStringUserInfo());
            DoSeeCard(betRoundId);
            isSeeCardThisTime = true;
        } else {
            /*
             * modify by gx 20181009 by bin 的需求
             * 通过随机概率判断是否执行看牌操作。
             * 如果随机数 大于 各轮次所配置的盲牌概率
             */
            int blindRandomNum = RandomTools.getRandomNum(100); //继续盲牌的概率
            BlindActionRateModel blindActionRateModel = RobotConfigHandler.getBlindActionRate(betRoundId);
            if (blindRandomNum > blindActionRateModel.getBlindRate()) {
                logger.warn("执行Action前，随机概率判断，应该看牌。{}, blindRandomNum->" + blindRandomNum + ", blindRate->" + blindActionRateModel.getBlindRate()+", betRoundId->"+betRoundId);
                DoSeeCard(betRoundId);
                isSeeCardThisTime = true;
            }
        }
        return isSeeCardThisTime;
    }

    /**
     * 当明牌（已看牌）状态，并且余额大于可用货币量A，执行机器人的下注、跟注、弃牌逻辑
     * @param playerInfo
     * @param betScore
     * @param betRoundId
     * @param baseBetScore
     * @param reqNum
     * @param limitBetScore
     */
    private void ExecActionBySeedCardAndMoreThanVariableA(PlayerInfo playerInfo, long betScore, int betRoundId, long baseBetScore, int reqNum, long limitBetScore){
        //修改原来的机器人行动概率获取方式
        int playTypeGameId = gameId;
        if(StringUtils.isNotBlank(currPlayType) && !currPlayType.equals(gameId)){
            playTypeGameId = Integer.parseInt(currPlayType);
        }
        logger.info("判断当前玩法。gameId->{}, currPlayType->{}, playTypeGameId->{}, {}", gameId, currPlayType, playTypeGameId, toStringUserInfo());

        RobotHandCardRateConfigModel rateModel = RobotConfigHandler.getHandCardRateConfigModel(playTypeGameId, cardModel, onTablePlayers.size());
        double actionRandomNum = RandomTools.getRandomDouble(100.00);

//        logger.info("非盲牌，可用货币量大于货币量A。" + toStringUserInfo() + ", playScore->" + playerInfo.getPlayScoreStore() + ", scoreVariable_A->" + scoreVariable_A + toStringReplaceLine()
//                + "     , card->" + cardModel.toStringCard() + ", cardType->" + cardModel.getTypeName() + ", foldRate->" + rateModel.getFoldRate() + ", showRate->" + rateModel.getShowRate() + toStringReplaceLine()
//                + "     , actionRandomNum->" + actionRandomNum + ", 未弃牌人数->" + getUnFoldPlayerCnt());

        if (actionRandomNum <= rateModel.getFoldRate()) { //弃牌
            logger.info("行为：随机概率-强制弃牌。{}" + toStringUserInfo());
            ExecuteFold(isCanShow, isCanSideShow, true, betRoundId, baseBetScore); //强制弃牌
        } else if (actionRandomNum > rateModel.getFoldRate() && actionRandomNum <= rateModel.getShowRate()) { //比牌
            logger.info("行为：随机概率-比牌。{}" + toStringUserInfo());
            ExecuteShowOrSideShow(gameOrderId, baseBetScore, reqNum, betRoundId, isCanShow, isCanSideShow, limitBetScore, false);
        } else { //下注
            if ((baseBetScore * 2 + baseBetScore) >= playerInfo.getPlayScoreStore()) {
                //如果余额不够加倍一次以上，那么执行比牌，如不能比牌，则弃牌
                logger.info("行为：随机概率-下注|余额不足，执行比牌/弃牌。" + toStringUserInfo() + toStringReplaceLine()
                        + "     , baseBetScore*2->" + baseBetScore * 2 + ", playScoreStore->" + playerInfo.getPlayScoreStore());
                ExecuteFold(isCanShow, isCanSideShow, false, betRoundId, baseBetScore);
                return;
            }

            double callRate = rateModel.getCallRate();
            double raiseRate = callRate / 2.0; //加注概率是跟注概率的一半
            double betRandomNum = RandomTools.getRandomDouble(100.00);
            if (betRandomNum <= raiseRate) { //加注
                betScore = baseBetScore * 2;
            }
            logger.info("行为：随机概率-下注|" + (betScore == baseBetScore ? "跟注" : "加注") + "。" + toStringUserInfo() + toStringReplaceLine()
                    + "     ,RaiseRateForSee->" + (100-rateModel.getCallRate()) + ", betRandomNum->" + betRandomNum);
            ExecuteBet(gameOrderId, baseBetScore, betScore, reqNum, betRoundId, limitBetScore, false);
        }
    }

    private boolean ExecShowOrSideShow(int isCanShow, int isCanSideShow, long baseBetScore){
        logger.debug("玩家{}执行ExecShowOrSideShow,isCanShow={},isCanSideShow={},baseBetScore={}",userId,isCanShow,isCanSideShow,baseBetScore);
        if (isCanShow == 1) {
            ExecuteApplyShow(); //show牌
            return true; //已执行
        }
        if (isCanSideShow == 1) {
            ExecuteGetSideShowState(baseBetScore);
            return true; //已执行
        }
        return false;
    }
    /**
     * 判断手牌是否 小于 期望值牌型
     * @param cardModel
     * @param onSeatPlayerCnt
     * @return
     */
    private static boolean isLessThanExpectCard(HandCardTypeModel cardModel, int onSeatPlayerCnt, String nickName){
        if(null == cardModel){
            return false;
        }

        HandCardTypeModel expectCardModel = RobotConfigHandler.getCardForExpect(onSeatPlayerCnt);
        if(null == expectCardModel){
            return false;
        }


        int resultCompare = cardModel.compareTo(expectCardModel);
        logger.warn("期望值手牌比较结果。nickName->{}, 手牌->{}, 期望值->{}, 比较结果->{}",
                nickName, cardModel.toStringCard(), expectCardModel.toStringCard(), resultCompare<=-1?"小于等于":"大于");
        if(resultCompare <= -1){
            //如果手牌小于期望值牌型，那么返回true
            return true;
        }

        return false;
    }

    /**
     * 判断手牌是否 大于等于 需要使用概率配置的牌型
     * @param cardModel
     * @param onSeatPlayerCnt
     * @return
     */
    private static boolean isMoreThanNeedConfigRateCard(HandCardTypeModel cardModel, int onSeatPlayerCnt, String nickName){
        if(null == cardModel){
            return false;
        }

        HandCardTypeModel needConfigRateCardModel = RobotConfigHandler.getCardForExecConfigRate(onSeatPlayerCnt);
        if(null == needConfigRateCardModel){
            return false;
        }

        int resultCompare = cardModel.compareTo(needConfigRateCardModel);
        logger.warn("需要执行概率的手牌比较结果。nickName->{}, 手牌->{}, 概率手牌->{}, 比较结果->{}",
                nickName, cardModel.toStringCard(), needConfigRateCardModel.toStringCard(), resultCompare >= 0?"大于等于":"小于");
        if(resultCompare >= 0){
            return true;
        }

        return false;
    }

    /**
     * 是否大于牌型 A 10 5
     * @param cardModel
     * @param nickName
     * @return
     */
    private static boolean isMoreThanAT5(HandCardTypeModel cardModel, String nickName){
        if(null == cardModel){
            return false;
        }

        HandCardTypeModel cardModelAT5 = new HandCardTypeModel(new CardValueModel('♠', 'A'), new CardValueModel('♥', 'T'), new CardValueModel('♦', '5'));
        int resultCompare = cardModel.compareTo(cardModelAT5);
        logger.warn("需要执行概率的手牌比较结果。nickName->{}, 手牌->{}, AT5手牌->{}, 比较结果->{}",
                nickName, cardModel.toStringCard(), cardModelAT5.toStringCard(), resultCompare >= 0?"大于等于":"小于");
        if(resultCompare >= 0){
            return true;
        }

        return false;
    }

    /**
     * 是否大于牌型 10 9 3
     * @param cardModel
     * @param nickName
     * @return
     */
    private static boolean isMoreThanT93(HandCardTypeModel cardModel, String nickName){
        if(null == cardModel){
            return false;
        }

        HandCardTypeModel cardModelT93 = new HandCardTypeModel(new CardValueModel('♠', 'T'), new CardValueModel('♥', '9'), new CardValueModel('♦', '3'));
        int resultCompare = cardModel.compareTo(cardModelT93);
        logger.warn("需要执行概率的手牌比较结果。nickName->{}, 手牌->{}, T93手牌->{}, 比较结果->{}",
                nickName, cardModel.toStringCard(), cardModelT93.toStringCard(), resultCompare >= 0?"大于等于":"小于");
        if(resultCompare >= 0){
            return true;
        }

        return false;
    }

    /**
     * 执行比牌，如果不能比牌，则做跟注操作
     */
    private void ExecuteShowOrSideShow(String gameOrderId, long baseBetScore, int reqNum, int betRoundId, int isCanShow, int isCanSideShow, long limitBetScore, boolean isBlind) {
        try{
            if (isCanShow == 1) {
                ExecuteApplyShow();
                return;
            }

            if (isCanSideShow == 1) {
                ExecuteGetSideShowState(baseBetScore);
                return;
            }

            PlayerInfo playerInfo = onTablePlayers.get(userId);
            if (null == playerInfo) {
                logger.error("ExecuteShowOrSideShow(). 用户已站起，playerInfo为空, {}", toStringUserInfo());
                ExecuteStandUp(); //再次执行站起，确保机器人执行离桌操作
                return;
            }
            //如果余额不足最低下注，那么弃牌
            if (playerInfo.getPlayScoreStore() < baseBetScore * 2) {
                logger.debug("行为：比牌|余额不足，强制弃牌。 {}, playScoreStore->{}, baseBetScore*2->{}", toStringUserInfo(), playerInfo.getPlayScoreStore(), baseBetScore * 2);
                ExecuteFold(isCanShow, isCanSideShow, true, betRoundId, baseBetScore);
            } else {
                logger.debug("行为：比牌|当前不允许比牌，做下注操作。 {}", toStringUserInfo());
                ExecuteBet(gameOrderId, baseBetScore, baseBetScore, reqNum, betRoundId, limitBetScore, isBlind);
            }
        } catch (Exception ex) {
            logger.error("ExecuteShowOrSideShow ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //region 下注
    public void ExecuteBet(String gameOrderId, long baseBetScore, long betScore, int reqNum, int betRoundId, long limitBetScore, boolean isBlind) {
        try {
            logger.debug("执行 下注 动作。, {}, baseBetScore->{}, betScore->{}", toStringUserInfo(), baseBetScore, betScore);
            if (betScore < baseBetScore) {
                betScore = baseBetScore;
            }

            if (betScore > limitBetScore) {
                betScore = limitBetScore;
            }

            PlayerInfo playerInfo = onTablePlayers.get(userId);
            if (null == playerInfo) {
                logger.error("ExecuteBet(). 用户已站起，playerInfo为空, {}", toStringUserInfo());
                ExecuteStandUp(); //再次执行站起，确保机器人执行离桌操作
                return;
            }
            int functionId = FunctionIdHolder.Game_REQ_ApplyBet;

            JoloGame.JoloGame_ApplyBetReq.Builder req = JoloGame.JoloGame_ApplyBetReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setGameOrderId(gameOrderId);
            req.setBetRoundId(betRoundId);
            req.setBetScore(betScore);

            logger.debug("GAME_REQ_ApplyBet Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", tableId->" + req.getTableId()
                    + ", gameOrderId->" + req.getGameOrderId() + ", isBlind->" + playerInfo.getIsBlind() + ", betScore->" + betScore);

            //延迟随机时间bet
            long delayMillSec = (long)(1000 * RandomTools.getRandomNum(5));
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask(){
                @Override
                public void run() {
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                }
            }, delayMillSec);
        } catch (Exception ex) {
            logger.error("ExecuteBet ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }
    //endregion

    //region 弃牌
    public void ExecuteFold(int isCanShow, int isCanSideShow, boolean isMustFold, int betRoundId, long baseBetScore) {
        try{
            /*
             * add by gx 20180929 增加逻辑：如果机器人和另外一个人打牌，轮次已经超过4轮，且机器人应该弃牌，那么直接执行比牌操作。
             */
            if(isCanShow == 1){
                logger.warn("机器人执行弃牌，但只剩两人，可以比牌，因此执行Show牌操作。betRoundId->{}, {}", betRoundId, toStringUserInfo());
                ExecuteApplyShow();
                return;
            }

            /*
             * add by gx 20181003 增加逻辑：为了使机器人看起来不是太容易弃牌，因此在第二轮开始，机器人将增加和其它玩家的sideShow比牌概率
             */
            int randomSideShowRate = RandomTools.getRandomNum(100);
            if(isCanShow == 0 && isCanSideShow == 1 && betRoundId >= 2){
                int sideShowRate = 80;
                if(randomSideShowRate <= sideShowRate){
                    logger.warn("机器人执行弃牌，但概率允许sideShow，因此执行sideShow操作。betRoundId->{}, {}", betRoundId, toStringUserInfo());
                    ExecuteGetSideShowState(baseBetScore);
                    return;
                }
            }

            if (!isMustFold) {
                if (isCanShow == 1) {
                    ExecuteApplyShow();
                    return;
                }

                if (isCanSideShow == 1) {
                    ExecuteGetSideShowState(baseBetScore);
                    return;
                }
            }

            logger.info("执行 弃牌 动作, isCanShow->{}, isCanSideShow->{}, betRound->{}, randomSideShowRate->{}, {}",
                    isCanShow, isCanSideShow, betRoundId, randomSideShowRate, toStringUserInfo());
            JoloGame.JoloGame_ApplyFoldReq.Builder req = JoloGame.JoloGame_ApplyFoldReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setGameOrderId(gameOrderId);
            req.setBetRoundId(betRoundId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_ApplyFold;

            logger.debug("GameReq_Fold Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId()
                    + ", gameOrderId->" + req.getGameOrderId());

            long delayMillSec = (long)RandomTools.getRandomNum(3000);
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask(){
                public void run(){
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                }}, delayMillSec+500);
        } catch (Exception ex) {
            logger.error("ExecuteFold ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }
    //endregion

    private void DoSeeCard(int betRoundId) {
        ExecuteSeeCard(betRoundId); //随机判断是否应该看牌
    }

    //region 登录 & 加入牌桌
    public void ExecuteLoginUser() {
        try{
            JoloAuth.JoloCommon_LoginReq.Builder req = JoloAuth.JoloCommon_LoginReq.newBuilder();
            req.setUserId(userId);
            req.setToken(userId);
            req.setClientVersion("guest");
            req.setChannelId("guest");
            req.setUserIp("127.0.0.1");
            req.setPlatform(0);
            req.setPlatformVersion("1.0.0");
            req.setDeviceNum("abcdefg-gfedcba");
            req.setVerify(verify);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            logger.debug("REQ ExecuteLoginUser, gameId->" + gameId + ", " + toStringUserInfo() + ", reqNum->" + reqNum);
            sendData(FunctionIdHolder.GATE_REQ_loginUser, gameId, reqNum, req.build().toByteArray());
        } catch (Exception ex) {
            logger.error("ExecuteLogin ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    public byte[] post(String url, byte[] bytes, String contentType) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        CloseableHttpClient m_HttpClient = HttpClients.createDefault();
        httpPost.setEntity(new ByteArrayEntity(bytes));
        if (contentType != null)
            httpPost.setHeader("Content-type", contentType);
        CloseableHttpResponse httpResponse = m_HttpClient.execute(httpPost);
        try {
            HttpEntity entityResponse = httpResponse.getEntity();
            int contentLength = (int) entityResponse.getContentLength();
            if (contentLength <= 0)
                throw new IOException("No response");
            byte[] respBuffer = new byte[contentLength];
            if (entityResponse.getContent().read(respBuffer) != respBuffer.length)
                throw new IOException("Read response buffer error");
            return respBuffer;
        } finally {
            httpResponse.close();
        }
    }

    private void AckLoginUser(int functionId, byte[] bytes) {
        try {
            JoloAuth.JoloCommon_LoginAck ack = JoloAuth.JoloCommon_LoginAck.parseFrom(bytes);
            logger.debug("ACK Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", ACK Body, result->" + ack.getResult() + ", ResultMsg->" + ack.getResultMsg() + ", userId->" + ack.getUserId()
                    + ", nickName->" + ack.getNickName() + ", money->" + ack.getMoney());

            if (ack.getResult() == 1 && ack.getMoney() > 0) {
                userId = ack.getUserId();
                verify = ack.getVerify();
                nickName = ack.getNickName();
                RobotClientHolder.addClient(userId, this);
                if (Config.TEST_TYPE_IS_STRESS == 1) {
                    ExecuteJoinTableForStressTest();
                } else {
                    ExecuteJoinTable();
                }
            } else {
                RobotClientHolder.closeClient(userId, this, "登录Game失败,result="+ack.getResult()+",money="+ack.getMoney());
                logger.error("login failed. userId->" + ack.getUserId() + ", money->" + ack.getMoney() + ", result->" + ack.getResult() + ", resultMsg->" + ack.getResultMsg());
            }
        } catch (Exception ex) {
            logger.error("AckLoginUser error, msg = " + ex.getMessage(), ex);
        }
    }

    public void ExecuteJoinTable() {
        try {
            JoloRoom.JoloRoom_ApplyJoinTableRobotReq.Builder req = JoloRoom.JoloRoom_ApplyJoinTableRobotReq.newBuilder();
            req.setGameId(gameId + "");
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            sendData(FunctionIdHolder.Room_REQ_ApplyJoinTable, gameId, reqNum, req.build().toByteArray());
        } catch (Exception ex) {
            logger.error("ExecuteJoinTable ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    private void ExecuteJoinTableForStressTest() {
        try {
            JoloRoom.JoloRoom_ApplyJoinTableReq.Builder req = JoloRoom.JoloRoom_ApplyJoinTableReq.newBuilder();
            req.setGameId(gameId + "");
            req.setUserId(userId);
            int functionId = FunctionIdHolder.Room_REQ_ApplyJoinTable_NoRoomId;
            int reqNum = (int) (System.currentTimeMillis() / 1000);
            sendData(functionId, gameId, reqNum, req.build().toByteArray());
        } catch (Exception ex) {
            logger.error("ExecuteJoinTableForStressTest ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }
    //endregion

    //region 申请站起
    private void ExecuteStandUp() {
        try {
            JoloGame.JoloGame_ApplyStandUpReq.Builder req = JoloGame.JoloGame_ApplyStandUpReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_ApplyStandUp;

            logger.debug("GameReq_StandUp Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId());

            long delayMillSec = (long) ((RandomTools.getRandomNum(4) + 2) * 1000);
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask() {
                public void run() {
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                    logger.info("执行站起操作, gameId->{}, userId->{}, gameSvrId->{}, roomId->{}, tableId->{}", gameId, userId, gameSvrId, roomId, tableId);
                }
            }, delayMillSec);
        } catch (Exception ex) {
            logger.error("ExecuteStandUp ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //region 买入
    private void ExecuteBuyIn(long buyInScore) {
        try {
            JoloGame.JoloGame_ApplyBuyInReq.Builder req = JoloGame.JoloGame_ApplyBuyInReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setBuyInScore(buyInScore);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_ApplyBuyIn;

            logger.debug("GameReq_BuyIn Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId()
                    + ", buyInScore->" + req.getBuyInScore());

            sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
        } catch (Exception ex) {
            logger.error("ExecuteBuyIn ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //region 看牌
    private void ExecuteSeeCard(int betRoundId) {
        try {
            if(gameId == 91001005 || this.currPlayType.equals("91001005")){
                //盲牌玩法，不能看牌。
                return;
            }
            logger.info("执行 看牌 动作。 nickName->{}, gameId->{}, currentPlayType->{}", nickName, gameId, currPlayType);
            JoloGame.JoloGame_ApplySeeCardReq.Builder req = JoloGame.JoloGame_ApplySeeCardReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setGameOrderId(gameOrderId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_ApplySeeCard;

            logger.debug("GameReq_SeeCard Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId()
                    + ", gameOrderId->" + req.getGameOrderId());

            long delayMillSec = (long)(1000 * RandomTools.getRandomNum(15));
//            long mustDelayMillSec = 500;
//            if(betRoundId == 1){
//                mustDelayMillSec = 4000; //以免客户端完成了发牌动画
//            }
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask(){
                public void run(){
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                }}, delayMillSec);
        } catch (Exception ex) {
            logger.error("ExecuteSeeCard ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }


    //region 申请单挑比牌
    private void ExecuteApplyShow() {
        try {
            logger.debug("执行比牌（Show）动作。, {}", toStringUserInfo());
            JoloGame.JoloGame_ApplyShowReq.Builder req = JoloGame.JoloGame_ApplyShowReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setGameOrderId(gameOrderId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_ApplyShow;

            logger.debug("GameReq_ApplyShow Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId()
                    + ", gameOrderId->" + req.getGameOrderId());

            long delayMillSec = (long)RandomTools.getRandomNum(2000);
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask(){
                public void run(){
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                }}, delayMillSec + 1000);
        } catch (Exception ex) {
            logger.error("ExecuteApplyShow ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //region 获得房间SideShow状态
    private void ExecuteGetSideShowState(long betScore) {
        try {
            logger.debug("执行 获得sideShow状态, betScore->{},  {}", betScore, toStringUserInfo());
            JoloGame.JoloGame_GetSideShowStateReq.Builder req = JoloGame.JoloGame_GetSideShowStateReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setGameOrderId(gameOrderId);
            req.setBetScore(betScore);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_GetSideShowState;

        logger.debug("GameReq_GetSideShowState Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId()
                + ", gameOrderId->" + req.getGameOrderId());

            long delayMillSec = (long)RandomTools.getRandomNum(2000);
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask(){
                public void run(){
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                }}, delayMillSec + 500);
        } catch (Exception ex) {
            logger.error("ExecuteGetSideShowState ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //是否同意比牌申请
    private void ExecuteIsAllowSideShow(int isAllow, String targetUserId) {
        try {
            JoloGame.JoloGame_IsAllowSideShowReq.Builder req = JoloGame.JoloGame_IsAllowSideShowReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setGameOrderId(gameOrderId);
            req.setIsAllow(isAllow);
            req.setTargetUserId(targetUserId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_IsAllowSideShow;

            logger.info("GameReq_IsAllowSideShow Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId()
                    + ", gameOrderId->" + req.getGameOrderId());

            //延迟但N秒内进行
            long delayMillSec = (long) ((RandomTools.getRandomNum(4) + 2) * 1000);
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask() {
                public void run() {
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                }
            }, delayMillSec);
        } catch (Exception ex) {
            logger.error("ExecuteIsAllowSideShow ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //打赏荷官
    private void ExecuteRewardCroupier(){
        try {
            logger.debug("执行 打赏荷官, {}", toStringUserInfo());
            JoloGame.JoloGame_RewardCroupierReq.Builder req = JoloGame.JoloGame_RewardCroupierReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_RewardCroupier;

            int rewardCount = RandomTools.getRandomNum(3);
            for (int i = 1; i <= rewardCount; i++) {
                //延迟但N秒内进行
                long delayMillSec = (long) (i * 500 + 2000);
                Timer timer = new Timer();//实例化Timer类
                timer.schedule(new TimerTask() {
                    public void run() {
                        sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                    }
                }, delayMillSec);
            }
        } catch (Exception ex) {
            logger.error("ExecuteRewardCroupier ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //赠送礼物
    private void ExecuteGiveGifts(){
        try{
            String key = gameId+"_"+roomId;
            if(logger.isDebugEnabled()) {
                logger.debug("giftsMap.key->{}, containsKey->{} ", key, GiftsHolder.giftsMap.containsKey(key));
            }
            if(GiftsHolder.giftsMap.containsKey(key)){
                List<JoloGame.JoloGame_ItemInfo> itemList = GiftsHolder.giftsMap.get(key);
                int randomItemIndex = RandomTools.getRandomNum(itemList.size())-1;
                JoloGame.JoloGame_ItemInfo itemInfo = itemList.get(randomItemIndex);

                logger.info("giftItemList->{}, randomItemIndex->{}, itemInfoIsNull->{}", itemList.size(), randomItemIndex, null == itemInfo);
                if(null != itemInfo) {
                    PlayerInfo playerInfo = onTablePlayers.get(userId);
                    if (null != playerInfo) {
                        long price = getBootAmount() * itemInfo.getItemPrice()/100;
                        logger.info("giveGift, itemName->{}, playerScore->{}, itemPrice->{}, ItemPrice*4->{}, playScoreStore/3->{}",
                                itemInfo.getItemName(), playerInfo.getPlayScoreStore(), price, price * 4, playerInfo.getPlayScoreStore()/3);
                        if (price * 4 <= playerInfo.getPlayScoreStore()/3) {
                            logger.info("执行 赠送礼物, itemId->{}, {}", itemInfo.getItemId(), toStringUserInfo());
                            JoloGame.JoloGame_GiveGiftsReq.Builder req = JoloGame.JoloGame_GiveGiftsReq.newBuilder();
                            req.setUserId(userId);
                            req.setRoomId(roomId);
                            req.setTableId(tableId);
                            req.setIsSendToAll(1);
                            req.setItemId(itemInfo.getItemId());
                            req.setTargetId(userId);

                            int reqNum = (int) (System.currentTimeMillis() / 1000);
                            int functionId = FunctionIdHolder.Game_REQ_GiveGifts;

                            long delayMillSec = (long) ((RandomTools.getRandomNum(2) + 1) * 1000);
                            Timer timer=new Timer();//实例化Timer类
                            timer.schedule(new TimerTask(){
                                public void run(){
                                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                                }}, delayMillSec);
                        }
                    }
                }
            }
        }catch (Exception ex){
            logger.error("ExecuteGiveGifts error, msg->"+ex.getMessage(), ex);
        }
    }

    private void ExecuteChoosePlayType(String playType){
        try {
            JoloGame.JoloGame_Choose_PlayTypeReq.Builder req = JoloGame.JoloGame_Choose_PlayTypeReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);
            req.setGameOrderId(gameOrderId);
            req.setPlayType(playType);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_ChoosePlayType;

            if (logger.isInfoEnabled()) {
                logger.info("ExecuteChoosePlayType Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId()
                        + ", gameOrderId->" + req.getGameOrderId() + ", playType->" + playType);
            }

            sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
        } catch (Exception ex) {
            logger.error("ExecuteChoosePlayType ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    //region 申请准备
    private void ExecuteReady() {
        try {
            if(alreadyPlayRound == 0){
                logger.warn("机器人第一局刚坐下，不执行准备操作。 nickName->{}", nickName);
                return; //第一局是自动坐下并准备，不需要手动准备。
            }

            logger.debug("执行准备（Ready）动作。, {}", toStringUserInfo());
            JoloGame.JoloGame_GamePrepareReq.Builder req = JoloGame.JoloGame_GamePrepareReq.newBuilder();
            req.setUserId(userId);
            req.setRoomId(roomId);
            req.setTableId(tableId);

            int reqNum = (int) (System.currentTimeMillis() / 1000);
            int functionId = FunctionIdHolder.Game_REQ_GamePrepareReq;

            logger.debug("ExecuteReady Body, reqNum->" + reqNum + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", " + toStringUserInfo() + ", roomId->" + req.getRoomId() + ", tableId->" + req.getTableId());

            long delayMillSec = (long) (RandomTools.getRandomNum(5) * 1000);
            Timer timer = new Timer();//实例化Timer类
            timer.schedule(new TimerTask() {
                public void run() {
                    sendData(functionId, gameId, reqNum, req.build().toByteArray(), gameSvrId);
                }
            }, delayMillSec+500);
        } catch (Exception ex) {
            logger.error("ExecuteReady ERROR," + toStringUserInfo() + ", ex = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_坐下
     */
    private void NoticeSitDown(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SitDownReq ack = JoloGame.JoloGame_Notice2Client_SitDownReq.parseFrom(bytes);
            logger.debug("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + toStringReplaceLine()
                    + ", " + toStringUserInfo() + ", seatNum->" + ack.getSeatNum() + ", playScore->" + ack.getPlayScore());

            if (roomId.equals(ack.getRoomId())
                    && tableId.equals(ack.getTableId())) {
                PlayerInfo playerInfo = new PlayerInfo();
                playerInfo.setUserId(ack.getUserId());
                playerInfo.setNickName(ack.getNickName());
                playerInfo.setSeatNum(ack.getSeatNum());
                playerInfo.setPlayScoreStore(ack.getPlayScore());
                playerInfo.setState(PlayerStateEnum.siteDown.getValue());

                onTablePlayers.put(ack.getUserId(), playerInfo);
                getInGamePlayersBySeatNum().put(ack.getSeatNum(), playerInfo);
            }
        } catch (Exception ex) {
            logger.error("NoticeSitDown error, " + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_站起
     */
    private void NoticeStandUp(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_StandUpReq ack = JoloGame.JoloGame_Notice2Client_StandUpReq.parseFrom(bytes);
            logger.info("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + toStringUserInfo() + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", seatNum->" + ack.getSeatNum());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId())) {
                PlayerInfo playerInfo = onTablePlayers.remove(ack.getUserId());
                if (playerInfo != null) {
                    standupPlayers.put(ack.getUserId(), playerInfo);
                }

                CheckForQuit(); //机器人判断自己是否应该站起
            }
        } catch (Exception ex) {
            logger.error("NoticeStandUp error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_离开
     */
    private void NoticeLeaveReq(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_leaveReq ack = JoloGame.JoloGame_Notice2Client_leaveReq.parseFrom(bytes);
            logger.info("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + toStringUserInfo() + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId())) {
                PlayerInfo playerInfo = onTablePlayers.remove(ack.getUserId());
                if (playerInfo != null) {
                    standupPlayers.put(ack.getUserId(), playerInfo);
                }

                CheckForQuit(); //机器人判断自己是否应该站起
            }
        } catch (Exception ex) {
            logger.error("NoticeStandUp error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_买入
     */
    private void NoticeBuyIn(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_BuyInReq ack = JoloGame.JoloGame_Notice2Client_BuyInReq.parseFrom(bytes);
            logger.debug("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + toStringReplaceLine()
                    + ", " + toStringUserInfo() + ", seatNum->" + ack.getSeatNum() + toStringReplaceLine()
                    + ", buyInScore->" + ack.getBuyInScore() + ", playScoreStore->" + ack.getPlayScoreStore());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId())) {
                PlayerInfo playerInfo = onTablePlayers.get(ack.getUserId());
                if (null == playerInfo) {
                    logger.info("NoticeBuyIn(). 用户已站起，playerInfo为空, userId->{}, nickName->{}", ack.getUserId(), getNickName(ack.getUserId()));
                } else {
                    playerInfo.setSeatNum(ack.getSeatNum());
                    playerInfo.setPlayScoreStore(ack.getPlayScoreStore());
                    updatePlayScoreStore(ack.getUserId(), ack.getPlayScoreStore(), true, gameOrderId, "买入Notice");
                }
            }
        } catch (Exception ex) {
            logger.error("NoticeBuyIn error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_牌局开始
     */
    private void NoticeGameStart(int functionId, byte[] bytes) {
        try {
            this._baseBetScore = 0;
            JoloGame.JoloGame_Notice2Client_GameStartReq ack = JoloGame.JoloGame_Notice2Client_GameStartReq.parseFrom(bytes);
            logger.debug("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + ", countDownSec->" + ack.getCountDownSec() + toStringReplaceLine()
                    + toStringUserInfo());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId())) {
                gameOrderId = ack.getGameOrderId();
                logger.debug("NoticeGameStart, 设置gameOrderId->{}, {}", ack.getGameOrderId(), toStringUserInfo());
                for (PlayerInfo playerInfo : onTablePlayers.values()) {
                    playerInfo.setIsBlind(1);
                }

                //在牌局开始时，根据概率判断是否要打赏荷官。 add by gx 20180720 16:01
                int ranomNum = RandomTools.getRandomNum(100);
                if(ranomNum <= Config.ROBOT_REWARD_CROUPIER){
                    ExecuteRewardCroupier();
                }
            }

            logger.info("NoticeGameStart()。 nickName->{}, isReady->{}, alreadyPlayRound->{}", nickName, this.isReady(), alreadyPlayRound);
            if(!this.isReady()) {
                ExecuteReady();
            }

            resetInitVariable();
        } catch (Exception ex) {
            logger.error("NoticeGameStart error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_发牌轮_开始
     */
    private void NoticeGiveCardRoundStart(int functionId, byte[] bytes) {
        try {
            resetInitVariable();

            JoloGame.JoloGame_Notice2Client_GiveCardRound_StartReq ack = JoloGame.JoloGame_Notice2Client_GiveCardRound_StartReq.parseFrom(bytes);
            logger.debug("NoticeGiveCardRoundStart Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", baseBetScore->" + ack.getBaseBetScore() + ", betRoundId->" + ack.getBetRoundId() + toStringReplaceLine()
                    + toStringNextActionPlayerInfo(ack.getNextActionPlayerInfo()) + toStringReplaceLine()
                    + toStringUserInfo());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId())) {
                gameOrderId = ack.getGameOrderId();
                logger.debug("NoticeGiveCardRoundStart, 设置gameOrderId->{}, {}", ack.getGameOrderId(), toStringUserInfo());
            }

            onTablePlayers.clear();
            for (JoloGame.JoloGame_TablePlay_PlayerInfo joloPlayerInfo : ack.getPlayerInfoListList()) {
                PlayerInfo playerInfo = new PlayerInfo();
                playerInfo.setUserId(joloPlayerInfo.getUserId());
                playerInfo.setNickName(joloPlayerInfo.getNickName());
                playerInfo.setSeatNum(joloPlayerInfo.getSeatNum());
                playerInfo.setPlayScoreStore(joloPlayerInfo.getPlayScoreStore());
                playerInfo.setState(PlayerStateEnum.gameing.getValue());
                playerInfo.setIsBlind(1);

                onTablePlayers.put(joloPlayerInfo.getUserId(), playerInfo);
                updatePlayScoreStore(joloPlayerInfo.getUserId(), joloPlayerInfo.getPlayScoreStore(), true, ack.getGameOrderId(),
                        "牌局开始-已下底注，ante->" + RoomConfigHolder.getInstance().getRoomConfig(ack.getRoomId()).getAnte());
                getInGamePlayersBySeatNum().put(joloPlayerInfo.getSeatNum(), playerInfo);
            }

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId()) && gameOrderId.equals(ack.getGameOrderId())) {
                PlayerInfo playerInfo = onTablePlayers.get(userId);
                if (null != playerInfo) {
                    //提前看牌
                    isNeedSeeCardThisRound(playerInfo, 1);
                }

                JoloGame.JoloGame_TablePlay_NextActionPlayerInfo nextInfo = ack.getNextActionPlayerInfo();
                //如果下一个行动玩家是机器人，那么调用下注逻辑
                if (nextInfo.getNextBetUserId().equals(userId)) {
                    logger.debug("第一个行动者是机器人。 {}" + toStringUserInfo());
                    Timer timer=new Timer();//实例化Timer类
                    timer.schedule(new TimerTask(){
                        public void run(){
                            ExecuteAction(ack.getGameOrderId(), nextInfo.getNextBetBaseBetScore(), nextInfo.getNextBetCountDownSec(), ack.getBetRoundId(), nextInfo.getNextBetIsCanShow(), nextInfo.getNextBetIsCanSideShow());
                        }},(long) ((RandomTools.getRandomNum(3) + 4) * 1000)); //延迟时间较长，避免出现机器人行动，但客户端还没展示完动画
                }
            }
        } catch (Exception ex) {
            logger.error("NoticeGiveCardRoundStart error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_下注轮_下注
     */
    private void NoticeBetRoundDoBet(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_BetRound_DoBetReq ack = JoloGame.JoloGame_Notice2Client_BetRound_DoBetReq.parseFrom(bytes);
            logger.debug("收到下注通知, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", betUserId->" + ack.getBetUserId() + ",betNickName->" + getNickName(ack.getBetUserId()) + ", BetUserSeatNum->" + ack.getBetUserSeatNum() + ", betScore->" + ack.getBetScore() + ", isBlind->" + ack.getIsBlind() + toStringReplaceLine()
                    + ", betRoundId->" + ack.getBetRoundId() + ". betSeqId->" + ack.getBetSeqId() + toStringReplaceLine()
                    + toStringNextActionPlayerInfo(ack.getNextActionPlayerInfo()) + toStringReplaceLine()
                    + toStringUserInfo());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId()) && gameOrderId.equals(ack.getGameOrderId())) {
                //修改用户积分缓存
                updatePlayScoreStore(ack.getBetUserId(), ack.getBetScore() * -1, false, ack.getGameOrderId(), "下注Notice");

                JoloGame.JoloGame_TablePlay_NextActionPlayerInfo nextInfo = ack.getNextActionPlayerInfo();
                //如果下一个行动玩家是机器人，那么调用下注逻辑
                logger.debug("下注操作玩家userid = {},curplayer = {}",nextInfo.getNextBetUserId(), userId);
                if (nextInfo.getNextBetUserId().equals(userId)) {
                    logger.debug("下一个行动者是机器人。 {}" + toStringUserInfo());
                    ExecuteAction(ack.getGameOrderId(), nextInfo.getNextBetBaseBetScore(), nextInfo.getNextBetCountDownSec(), ack.getBetRoundId(), nextInfo.getNextBetIsCanShow(), nextInfo.getNextBetIsCanSideShow());
                }
            }
        } catch (Exception ex) {
            logger.error("NoticeBetRoundDoBet error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_玩家看牌（取消盲牌）
     */
    private void NoticePlayerSeeCard(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_PlayerSeeCardReq ack = JoloGame.JoloGame_Notice2Client_PlayerSeeCardReq.parseFrom(bytes);

            logger.debug("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", seeCardUserId->" + ack.getUserId() + ", seeCardNickName->" + getNickName(ack.getUserId()) + ", seatNum->" + ack.getSeatNum() + ", currActionPlayerId->" + ack.getCurrActionPlayerId() + toStringReplaceLine()
                    + ", isCanShow->" + ack.getIsCanShow() + ". isCanSideShow->" + ack.getIsCanSideShow() + ", betScore->" + ack.getBetScore() + toStringReplaceLine()
                    + toStringUserInfo());

            PlayerInfo playerInfo = onTablePlayers.get(ack.getUserId());
            if (null == playerInfo) {
                logger.info("NoticePlayerSeeCard(). 用户已站起，playerInfo为空, userId->{}, nickName->{}", ack.getUserId(), getNickName(ack.getUserId()));
            } else {
                playerInfo.setIsBlind(0);
            }
        } catch (Exception ex) {
            logger.error("NoticePlayerSeeCard error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_比牌_申请比牌
     */
    private void NoticeApplySideShow(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SideShow_ApplySideShowReq ack = JoloGame.JoloGame_Notice2Client_SideShow_ApplySideShowReq.parseFrom(bytes);
            //修改用户积分缓存
            updatePlayScoreStore(ack.getSideShowUserId(), ack.getSideShowBetScore() * -1, false, ack.getGameOrderId(), "SideShowNotice");

            logger.info("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", SideShowUserId->" + ack.getSideShowUserId() + ", SideShowNickName->" + getNickName(ack.getSideShowUserId()) + ", SideShowUserSeatNum->" + ack.getSideShowUserSeatNum() + ", SideShowBetScore->" + ack.getSideShowBetScore() + toStringReplaceLine()
                    + ", TargetUserId->" + ack.getTargetUserId() + ", TargetNickName->" + getNickName(ack.getTargetUserId()) + ". TargetUserSeatNum->" + ack.getTargetUserSeatNum() + ", CountDownSec->" + ack.getCountDownSec() + toStringReplaceLine()
                    + toStringUserInfo());
        } catch (Exception ex) {
            logger.error("NoticeApplySideShow error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_比牌_等待目标玩家同意比牌申请
     */
    private void NoticeWaitUserAllow(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SideShow_WaitTargetUserAllowReq ack = JoloGame.JoloGame_Notice2Client_SideShow_WaitTargetUserAllowReq.parseFrom(bytes);
            logger.info("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", WaitAllowUserId->" + ack.getUserId() + ", WaitAllowNickName->" + getNickName(ack.getUserId()) + ", CountDownSec->" + ack.getCountDownSec() + toStringUserInfo());

            if (ack.getUserId().equals(userId)) {
                PlayerInfo playerInfo = onTablePlayers.get(userId);
                if (null == playerInfo) {
                    logger.error("NoticeWaitUserAllow(). 用户已站起，playerInfo为空, userId->{}, nickName->{}", userId, getNickName(userId));
                    ExecuteStandUp(); //再次执行站起操作，确保机器人操作离桌
                    return;
                }
                if (null == cardModel) {
                    logger.info("机器人是盲注状态，不允许玩家的比牌操作。" + toStringUserInfo());
                    ExecuteIsAllowSideShow(0, ack.getUserId());
                } else {
                    RoomConfigModel roomConfigModel = RoomConfigHolder.getInstance().getRoomConfig(roomId);
                    long limitBetScore = roomConfigModel.getMaxBetScore();
                    long scoreVariable_A = getScoreVariable_A(this._baseBetScore * 2, limitBetScore);
                    if (playerInfo.getPlayScoreStore() > scoreVariable_A) { //大于货币量A
//                        int cardType = getHandCardType(cardModel);
//                        String cardLevel = RobotConfigHandler.getCardLevel(gameId + "", cardType);
//                        RobotConfigForPlaytypeModel cardLevelConfig = RobotConfigHandler.getRobotConfigForPlaytype(gameId + "", cardLevel);

                        int playTypeGameId = gameId;
                        if(StringUtils.isNotBlank(currPlayType) && !currPlayType.equals(gameId)){
                            playTypeGameId = Integer.parseInt(currPlayType);
                        }
                        logger.info("判断当前玩法。gameId->{}, currPlayType->{}, playTypeGameId->{}, {}", gameId, currPlayType, playTypeGameId, toStringUserInfo());
                        RobotHandCardRateConfigModel rateModel = RobotConfigHandler.getHandCardRateConfigModel(playTypeGameId, cardModel, onTablePlayers.size());
                        double actionRandomNum = RandomTools.getRandomDouble(100.00);
                        if (actionRandomNum <= rateModel.getAgreeSideShowRate()) {
                            ExecuteIsAllowSideShow(1, ack.getUserId());
                            logger.info("同意比牌的概率, gameId->{}, currPlayType->{}, card->{}, actionRandomNum->{}, agreeSideShowRate->{}", gameId, currPlayType, cardModel.toStringCard(), actionRandomNum, rateModel.getAgreeSideShowRate());
                        } else {
                            ExecuteIsAllowSideShow(0, ack.getUserId());
                        }
                    } else {
                        ExecuteIsAllowSideShow(0, ack.getUserId());
                    }
                }
            }
        } catch (Exception ex) {
            logger.error("NoticeWaitUserAllow error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_比牌_等待玩家选择指定的用户
     */
    private void NoticeWaitChooseUser(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SideShow_WaitChooseUserReq ack = JoloGame.JoloGame_Notice2Client_SideShow_WaitChooseUserReq.parseFrom(bytes);
            logger.info("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", WaitChooseUserId->" + ack.getUserId() + ", WaitChooseNickName->" + getNickName(ack.getUserId()) + ", SeatNum->" + ack.getSeatNum() + ", CountDownSec->" + ack.getCountDownSec() + toStringReplaceLine()
                    + toStringUserInfo());
        } catch (Exception ex) {
            logger.error("NoticeWaitChooseUser error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_比牌_显示SideShow比牌结果
     */
    private void NoticeSideShowResult(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SideShow_SideShowResultReq ack = JoloGame.JoloGame_Notice2Client_SideShow_SideShowResultReq.parseFrom(bytes);
            logger.info("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", WinPlayer_UserId->" + ack.getWinPlayerUserId() + ", WinPlayer_NickName->" + getNickName(ack.getWinPlayerUserId()) + ", WinPlayer_SeatNum->" + ack.getWinPlayerSeatNum() + toStringReplaceLine()
                    + ", LosePlayer_UserId->" + ack.getLosePlayerUserId() + ", LosePlayer_NickName->" + getNickName(ack.getLosePlayerUserId()) + ", LosePlayer_SeatNum->" + ack.getLosePlayerSeatNum() + ", CountDownSec->" + ack.getCountDownSec() + toStringReplaceLine()
                    + toStringNextActionPlayerInfo(ack.getNextActionPlayerInfo()) + toStringReplaceLine()
                    + toStringUserInfo());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId()) && gameOrderId.equals(ack.getGameOrderId())) {
                String loseUserId = ack.getLosePlayerUserId();
                PlayerInfo losePlayerInfo = onTablePlayers.get(loseUserId);
                if (null != losePlayerInfo) {
                    losePlayerInfo.setState(PlayerStateEnum.fold.getValue());
                    logger.debug("更新比牌输家的状态为弃牌。 loseUserId->{}, loseNickName->{}, 牌局总人数->{}, 当前未弃牌人数->{}, {}",
                            losePlayerInfo.getUserId(), losePlayerInfo.getNickName(), onTablePlayers.size(), getUnFoldPlayerCnt(),
                            toStringReplaceLine() + toStringUserInfo());
                }

                JoloGame.JoloGame_TablePlay_NextActionPlayerInfo nextInfo = ack.getNextActionPlayerInfo();
                //如果下一个行动玩家是机器人，那么调用下注逻辑
                if (nextInfo.getNextBetUserId().equals(userId)) {
                    logger.debug("下一个行动者是机器人。 {}" + toStringUserInfo());
                    ExecuteAction(ack.getGameOrderId(), nextInfo.getNextBetBaseBetScore(), nextInfo.getNextBetCountDownSec(), nextInfo.getBetRoundId(), nextInfo.getNextBetIsCanShow(), nextInfo.getNextBetIsCanSideShow());
                }
            }
        } catch (Exception ex) {
            logger.error("NoticeSideShowResult error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_通知结算结果
     */
    private void NoticeSettleResult(int functionId, byte[] bytes) {
        alreadyPlayRound++;
        try {
            JoloGame.JoloGame_Notice2Client_SettleRound_SettleReq ack = JoloGame.JoloGame_Notice2Client_SettleRound_SettleReq.parseFrom(bytes);
            StringBuilder logStr = new StringBuilder();
            logStr.append("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + toStringUserInfo() + toStringReplaceLine());

            toStringPlayersScoreStore(ack.getGameOrderId());

            for (JoloGame.JoloGame_TablePlay_PlayerSettleInfo settleInfo : ack.getSettleInfoListList()) {
                //修改用户积分缓存
                updatePlayScoreStore(settleInfo.getUserId(), settleInfo.getPlayScoreStore(), true, ack.getGameOrderId(), "结算结果");

                //判断是否需要赠送礼物给全桌（如果赢钱玩家是机器人，那么按照概率随机执行赠送礼物给全桌）
                if(settleInfo.getUserId().equals(userId)
                        && settleInfo.getWinLoseScore() > (getBootAmount() * Config.ROBOT_GIVE_GIFT_WIN_BOOTAMOUNT)){
                    logStr.append("PlayerSettleInfo->" + toStringReplaceLine());
                    logStr.append("    UserId->" + settleInfo.getUserId() + ",NickName->" + getNickName(settleInfo.getUserId()) + ", SeatNum->" + settleInfo.getSeatNum()
                            + ", WinLoseScore->" + settleInfo.getWinLoseScore() + ", PlayScoreStore->" + settleInfo.getPlayScoreStore() + toStringReplaceLine());
                    if (settleInfo.getHandCardsCount() == 3) {
                        logger.warn(", HandCards->" + new HandCardTypeModel(settleInfo.getHandCards(0), settleInfo.getHandCards(1), settleInfo.getHandCards(2)).toStringCard()
                                + toStringUserInfo());
                    }

                    logger.debug("userId->{}, nickName->{}, winLoseScore->{}, bootAmount->{}, value->{}",
                            settleInfo.getUserId(), getNickName(settleInfo.getUserId()), settleInfo.getWinLoseScore(), getBootAmount(), (getBootAmount() * Config.ROBOT_GIVE_GIFT_WIN_BOOTAMOUNT));
                    int ranomNum = RandomTools.getRandomNum(100);
                    if(ranomNum <= Config.ROBOT_GIVE_GIFT){
                        ExecuteGiveGifts();
                    }
                }
            }
            logger.debug(logStr.toString());

            logger.debug("收到结算消息：onTablePlayers->" + onTablePlayers.size() + ", null != onTablePlayers.get(userId)->" + (null != onTablePlayers.get(userId)) + toStringUserInfo());

//            if (onTablePlayers.size() <= 1
//                    && null != onTablePlayers.get(userId)) {
//                logger.debug("当牌桌只剩机器人自己时，触发站起逻辑");
//                ExecuteStandUp();  //当牌桌只剩机器人自己时，触发站起逻辑
//                return;
//            }

            //强制机器人离桌（逻辑修改by gx 20181010 机器人只玩一局牌，然后就离桌）
            ExecuteStandUp();

            resetInitVariable();

            //清除Dealer玩法时保存的临时缓存数据
            this.currPlayType = "";
            this.isNeedFoldBy91001005 = false;
        } catch (Exception ex) {
            logger.error("NoticeSettleResult error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * 结算历史
     */
    private void NoticeSettleHistory(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SettleRound_HistoryReq ack = JoloGame.JoloGame_Notice2Client_SettleRound_HistoryReq.parseFrom(bytes);
            logger.debug("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + ", Body, GameOrderId->" + ack.getGameOrderId() + ", size->" + ack.getHistoryList().size()
                    + ", alreadyPlayRound->" + alreadyPlayRound + toStringUserInfo());

//            if (alreadyPlayRound > playRoundCount) {
//                logger.debug("达到已玩局数，站起机器人。已玩局数->" + alreadyPlayRound + ", 应玩局数->" + playRoundCount);
//                ExecuteStandUp();
//            }
        } catch (Exception ex) {
            logger.error("NoticeSettleHistory error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_比牌_用户选择完成了被比牌用户
     */
    private void NoticeSideShowCompleteChooseUser(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SideShow_CompleteChooseUserReq ack = JoloGame.JoloGame_Notice2Client_SideShow_CompleteChooseUserReq.parseFrom(bytes);
            StringBuilder logStr = new StringBuilder();
            logStr.append("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", ApplyUserId->" + ack.getApplyUserId() + ", ApplyUserNickName->" + getNickName(ack.getApplyUserId()) + ", ApplyUserSeatNum->" + ack.getApplyUserSeatNum() + toStringReplaceLine()
                    + ", TargetUserId->" + ack.getTargetUserId() + ", TargetNickName->" + getNickName(ack.getTargetUserId()) + ", TargetUserSeatNum->" + ack.getTargetUserSeatNum() + toStringReplaceLine()
                    + toStringUserInfo());
            logger.debug(logStr.toString());
        } catch (Exception ex) {
            logger.error("NoticeSideShowCompleteChooseUser error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_比牌_用户完成了是否允许比牌的选择
     */
    private void NoticeSideShowCompleteAllow(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_SideShow_CompleteAllowReq ack = JoloGame.JoloGame_Notice2Client_SideShow_CompleteAllowReq.parseFrom(bytes);
            StringBuilder logStr = new StringBuilder();
            logStr.append("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", ApplyUserId->" + ack.getApplyUserId() + ", ApplyUserNickName->" + getNickName(ack.getApplyUserId()) + ", ApplyUserSeatNum->" + ack.getApplyUserSeatNum() + toStringReplaceLine()
                    + ", TargetUserId->" + ack.getTargetUserId() + ", TargetNickName->" + getNickName(ack.getTargetUserId()) + ", TargetUserSeatNum->" + ack.getTargetUserSeatNum() + ", TargetUserIsAllow->" + ack.getTargetUserIsAllow() + toStringReplaceLine()
                    + toStringNextActionPlayerInfo(ack.getNextActionPlayerInfo()) + toStringReplaceLine()
                    + toStringUserInfo());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId()) && gameOrderId.equals(ack.getGameOrderId())) {
                JoloGame.JoloGame_TablePlay_NextActionPlayerInfo nextInfo = ack.getNextActionPlayerInfo();
                //如果下一个行动玩家是机器人，那么调用下注逻辑
                if (nextInfo.getNextBetUserId().equals(userId)) {
                    logger.debug("NoticeSideShowCompleteAllow, 下一个行动者是机器人。 {}" + toStringUserInfo());

//                    long delayMillSec = (long)(RandomTools.getRandomNum(2) * 1000) + 500;
//                    Timer timer=new Timer();//实例化Timer类
//                    timer.schedule(new TimerTask(){
//                        public void run(){
//                            ExecuteAction(ack.getGameOrderId(), nextInfo.getNextBetBaseBetScore(), nextInfo.getNextBetCountDownSec(), nextInfo.getBetRoundId(), nextInfo.getNextBetIsCanShow(), nextInfo.getNextBetIsCanSideShow());
//                        }}, delayMillSec);

                    ExecuteAction(ack.getGameOrderId(), nextInfo.getNextBetBaseBetScore(), nextInfo.getNextBetCountDownSec(), nextInfo.getBetRoundId(), nextInfo.getNextBetIsCanShow(), nextInfo.getNextBetIsCanSideShow());
                }
            }
            logger.debug(logStr.toString());
        } catch (Exception ex) {
            logger.error("NoticeSettleResult error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_用户弃牌
     */
    private void NoticeBetRoundFold(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_BetRound_FoldReq ack = JoloGame.JoloGame_Notice2Client_BetRound_FoldReq.parseFrom(bytes);
            StringBuilder logStr = new StringBuilder();
            logStr.append("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + ", ApplyUserId->" + ack.getApplyUserId() + ", ApplyNickName->" + getNickName(ack.getApplyUserId()) + ", ApplyUserSeatNum->" + ack.getApplyUserSeatNum()
                    + toStringNextActionPlayerInfo(ack.getNextActionPlayerInfo()) + toStringReplaceLine()
                    + toStringUserInfo());
            logger.debug(logStr.toString());

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId()) && gameOrderId.equals(ack.getGameOrderId())) {
                PlayerInfo playerInfo = onTablePlayers.get(ack.getApplyUserId());
                if (null == playerInfo) {
                    logger.info("NoticeBetRoundFold(). 用户已站起，playerInfo为空, userId->{}, nickName->{}", ack.getApplyUserId(), getNickName(ack.getApplyUserId()));
                } else {
                    playerInfo.setIsBlind(0);
                    playerInfo.setState(PlayerStateEnum.fold.getValue());
                    playerInfo.setSeatNum(ack.getApplyUserSeatNum());
                }

                JoloGame.JoloGame_TablePlay_NextActionPlayerInfo nextInfo = ack.getNextActionPlayerInfo();
                //如果下一个行动玩家是机器人，那么调用下注逻辑
                if (nextInfo.getNextBetUserId().equals(userId)) {
                    logger.debug("NoticeBetRoundFold, 下一个行动者是机器人。 {}" + toStringUserInfo());
//                    long delayMillSec = (long)(RandomTools.getRandomNum(2) * 1000) + 500;
//                    Timer timer=new Timer();//实例化Timer类
//                    timer.schedule(new TimerTask(){
//                        public void run(){
//                            ExecuteAction(ack.getGameOrderId(), nextInfo.getNextBetBaseBetScore(), nextInfo.getNextBetCountDownSec(), nextInfo.getBetRoundId(), nextInfo.getNextBetIsCanShow(), nextInfo.getNextBetIsCanSideShow());
//                        }}, delayMillSec);
                    ExecuteAction(ack.getGameOrderId(), nextInfo.getNextBetBaseBetScore(), nextInfo.getNextBetCountDownSec(), nextInfo.getBetRoundId(), nextInfo.getNextBetIsCanShow(), nextInfo.getNextBetIsCanSideShow());
                }
            }
        } catch (Exception ex) {
            logger.error("NoticeBetRoundFold error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    /**
     * Notice_超过桌子限制的总分后提醒玩家进入结算
     */
    private void NoticeSettlePrompt(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_Settle_PromptReq ack = JoloGame.JoloGame_Notice2Client_Settle_PromptReq.parseFrom(bytes);
            StringBuilder logStr = new StringBuilder();
            logStr.append("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + toStringReplaceLine()
                    + toStringUserInfo());
            logger.debug(logStr.toString());
        } catch (Exception ex) {
            logger.error("NoticeSettlePrompt error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    //Hukam混牌通知
    private void NoticeMixedCard(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_Mixed_CardReq ack = JoloGame.JoloGame_Notice2Client_Mixed_CardReq.parseFrom(bytes);
            StringBuilder logStr = new StringBuilder();
            logStr.append("Notice Header, functionId->" + functionId + ", functionName->" + FunctionIdHolder.GetFunctionName(functionId) + toStringReplaceLine()
                    + ", Body, roomId->" + ack.getRoomId() + ", tableId->" + ack.getTableId() + ", gameOrderId->" + ack.getGameOrderId() + ", mixedCardId->" + ack.getMixedCardId() + toStringReplaceLine()
                    + toStringUserInfo());
            if (ack.getRoomId().equals(roomId) && ack.getTableId().equals(tableId) && ack.getGameOrderId().equals(gameOrderId)) {
                this.mixedCardIndex = ack.getMixedCardId();
            }
            logger.debug(logStr.toString());
        } catch (Exception ex) {
            logger.error("NoticeMixedCard error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    private void NoticeChoosePlayType(int functionId, byte[] bytes) {
        try {
            JoloGame.JoloGame_Notice2Client_Choose_PlayTypeReq ack = JoloGame.JoloGame_Notice2Client_Choose_PlayTypeReq.parseFrom(bytes);

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId()) && gameOrderId.equals(ack.getGameOrderId())
                    && userId.equals(ack.getDealerId())) {
                long delayMillSec = (long) (RandomTools.getRandomNum(4) * 1000) + 500;
                Timer timer = new Timer();//实例化Timer类
                timer.schedule(new TimerTask() {
                    public void run() {
                        List<String> playTypeList = ack.getPlayTypeList();
                        int randomNum = RandomTools.getRandomNum(playTypeList.size())-1;
                        String randomPlayType = playTypeList.get(randomNum);
//                        logger.warn("randomNum->{}, randomPlayType->{}, listSize->{}", randomNum, randomPlayType, playTypeList.size());
                        ExecuteChoosePlayType(randomPlayType);
                    }
                }, delayMillSec);
            }
        } catch (Exception ex) {
            logger.error("NoticeSettleResult error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }

    private void NoticeCurrPlayType(int functionId, byte[] bytes){
        try{
            JoloGame.JoloGame_Notice2Client_PlayTypeReq ack = JoloGame.JoloGame_Notice2Client_PlayTypeReq.parseFrom(bytes);

            if (roomId.equals(ack.getRoomId()) && tableId.equals(ack.getTableId()) && gameOrderId.equals(ack.getGameOrderId())) {
                this.currPlayType = ack.getPlayType();
                List<String> robotUserList = getRobotUserList();

                //如果有两个机器人，并且玩法是BLind时，那么有一个机器人要执行弃牌操作
                if(robotUserList.size() == 2 && currPlayType.equals("91001005")){
                    if(robotUserList.get(0).equals(userId)){ //如果需要站起机器人的ID 与 当前机器人ID 相同，那么站起当前机器人
                        this.isNeedFoldBy91001005 = true;
//                        logger.warn("当前玩法是Blind，当前机器人应该主动弃牌。userId->{}, roomId->{}, tableId->{}", userId, roomId, tableId);
                    }
                }
            }
        }catch (Exception ex){
            logger.error("NoticeCurrPlayType error," + toStringUserInfo() + ", msg = " + ex.getMessage(), ex);
        }
    }
    //endregion

    private String toStringNextActionPlayerInfo(JoloGame.JoloGame_TablePlay_NextActionPlayerInfo nextActionPlayerInfo) {
        if (null == nextActionPlayerInfo) {
            return "";
        }

        return "nextBetUserId->" + nextActionPlayerInfo.getNextBetUserId()
                + ", nextBetNickName->" + getNickName(nextActionPlayerInfo.getNextBetUserId())
                + ", nextBetUserSeatNum->" + nextActionPlayerInfo.getNextBetUserSeatNum()
                + ", nextBetCountDownSec->" + nextActionPlayerInfo.getNextBetCountDownSec()
                + ", nextBetIsCanSideShow->" + nextActionPlayerInfo.getNextBetIsCanSideShow()
                + ", nextBetIsCanShow->" + nextActionPlayerInfo.getNextBetIsCanShow()
                + ", nextBetBaseBetScore->" + nextActionPlayerInfo.getNextBetBaseBetScore();
    }

    private String getNickName(String userId) {
        PlayerInfo playerInfo = onTablePlayers.get(userId);
        if (null != playerInfo) {
            return playerInfo.getNickName();
        }

        User user = EntityProxy.OBJ.get(userId, User.class);
        if (null != user) {
            return user.getNick_name();
        }
        return "UNKNOWN";
    }


    private int getHandCardType(HandCardTypeModel cardModel) {
        if (null != cardModel) {
            return cardModel.getTypeCompareValue();
        }
        return 0;
    }

    /**
     * 获得牌局未弃牌人数
     */
    private int getUnFoldPlayerCnt() {
        int unFoldPlayerCnt = 0;
        for (PlayerInfo playerInfo : onTablePlayers.values()) {
            if (playerInfo.getState() != PlayerStateEnum.fold.getValue()
                    && playerInfo.getState() != PlayerStateEnum.siteDown.getValue()
                    && playerInfo.getState() != PlayerStateEnum.spectator.getValue()) {
                unFoldPlayerCnt = unFoldPlayerCnt + 1;
            }
        }
        return unFoldPlayerCnt;
    }

    /**
     * 获取桌内真人数量
     */
    private int getRealUserCnt() {
        int cnt = 0;
        for (PlayerInfo playerInfo : onTablePlayers.values()) {
            if (RobotUserHolder.getRobotMap().containsKey(playerInfo.getUserId()) == false) {
                cnt = cnt + 1;
            }
        }
        return cnt;
    }

    /**
     * 获取桌内机器人数量
     */
    private List<String> getRobotUserList(){
        List<String> pList = new ArrayList<String>();
        for(PlayerInfo playerInfo : onTablePlayers.values()){
            if(RobotUserHolder.getRobotMap().containsKey(playerInfo.getUserId()) == true){
                pList.add(playerInfo.getUserId());
            }
        }
        Collections.sort(pList);
        return pList;
    }

    /**
     * 获得货币量 A 的值
     */
    private long getScoreVariable_A(long maxBetScore, long limitScore) {
        long variable_A = 0;
        if (maxBetScore >= limitScore) {
            variable_A = maxBetScore + limitScore;
        } else {
            variable_A = maxBetScore + (maxBetScore * (long) Math.pow(2, getUnFoldPlayerCnt() - 1));
        }

        if (variable_A > limitScore * 2) {
            logger.debug("可用货币量A大于两倍单封值，那么可用货币量A等于两倍单封值。variable_A->{}, limitScore->{}, limitScore*2->{}", variable_A, limitScore, limitScore * 2);
            variable_A = limitScore * 2;
        }
        return variable_A;
    }

    public void updatePlayScoreStore(String userId, double amount, boolean isAll, String gameOrderId, String updateType) {
        PlayerInfo playerInfo = onTablePlayers.get(userId);
        if (null == playerInfo) {
            logger.info("Function:updatePlayScoreStore(), 用户已站起， onTablePlayers中找不到指定用户。 userId->{}, nickName->{}", userId, getNickName(userId));
            return;
        }
        double oldPlayScoreStore = playerInfo.getPlayScoreStore();
        if (isAll) {
            playerInfo.setPlayScoreStore(amount);
        } else {
            playerInfo.setPlayScoreStore(playerInfo.getPlayScoreStore() + amount);
        }
        logger.debug("更新用户积分, userId->{}, nickName->{}, updateType->{}, oldScore->{}, amount->{}, newScore->{}, gameOrderId->{}. {}",
                playerInfo.getUserId(), playerInfo.getNickName(), updateType, oldPlayScoreStore, amount, playerInfo.getPlayScoreStore(), gameOrderId
                , toStringReplaceLine() + toStringUserInfo());
    }

    /**
     * 重置初始化变量（每局开始时重置）
     */
    private void resetInitVariable() {
        this.isCanShow = 0;
        this.isCanSideShow = 0;
        this._baseBetScore = 0;
        setCardModel(null);
        this.cardModel = null;
        standupPlayers.clear();
        this.mixedCardIndex = 0;
        setReady(false);
    }

    /**
     * 检查机器人是否需要退出
     */
    private void CheckForQuit() {
        long moneyToQuit = RoomConfigHolder.getInstance().getRoomConfig(roomId).getAnte() * 64;
        PlayerInfo playerInfo = onTablePlayers.get(userId);
        if (null == playerInfo) {
            logger.info("CheckForQuit(), 用户已站起，onTablePlayers中找不到指定用户，{}", toStringUserInfo());
            ExecuteStandUp(); //再次执行站起，确保机器人执行离桌操作
            return;
        }

        if(alreadyPlayRound >= randomRound){
            logger.info("机器人达到应玩场次限制。 alreadyPlayRound->{}, randomRound->{}", alreadyPlayRound, randomRound);
            ExecuteStandUp();
            return;
        }

        //余额达到退桌标准
        if (playerInfo.getPlayScoreStore() <= moneyToQuit) {
            logger.warn("站起机器人，机器人余额达到退桌标准，moneyToQuit->{}, playScoreStore->{}, alreadyPlayRound->{}，{}",
                    moneyToQuit, playerInfo.getPlayScoreStore(), alreadyPlayRound, toStringUserInfo());
            if (Config.TEST_TYPE_IS_STRESS == 0) {
                ExecuteStandUp();
                return;
            }else{
                ExecuteBuyIn(5000);
            }
        }


        //人数达到退桌标准
        if(Config.TEST_TYPE_IS_STRESS == 0){ //非压测环境下，判断真人数量
            //判断真人数量
            int realUserCnt = getRealUserCnt();
            List<String> robotUserList = getRobotUserList();

            //如果桌内有三个真人，并且有两个以上机器人时，站起其中一个机器人
            //注释 by gx 20181009 因为只打一局就散桌，所以不强制5个人时发生离桌行为
//            if(realUserCnt == 3 && robotUserList.size() >= 2){
//                if(robotUserList.get(0).equals(userId)){ //如果需要站起机器人的ID 与 当前机器人ID 相同，那么站起当前机器人
//                    logger.warn("站起机器人，达到3个真人，2个机器人的条件，{}", toStringUserInfo());
//                    ExecuteStandUp();
//                    return;
//                }
//            }

            //者如果桌内有四个以上真人，那么站起全部机器人
            //注释 by gx 20181009 因为只打一局就散桌，所以不强制5个人时发生离桌行为
//            if(realUserCnt >= 4) {
//                logger.warn("站起机器人，Robot need Quit by RealUserCnt >= 4，{}, realUserCnt->{}",
//                        toStringUserInfo(), realUserCnt);
//                ExecuteStandUp();
//                return;
//            }

            //如果桌内没有真人时，站起机器人。(必要条件：1、机器人不可加入空桌；)
            //注释 by gx 20181009 因为只打一局就散桌，所以不强制5个人时发生离桌行为
//            if(realUserCnt == 0 && Config.ROBOT_JOIN_NULL_TABLE == 0){
//                logger.warn("站起机器人，Robot need Quit by RealUserCnt == 0，{}, realUserCnt->{}, Config.ROBOT_JOIN_NULL_TABLE->{}",
//                        toStringUserInfo(), realUserCnt, Config.ROBOT_JOIN_NULL_TABLE);
//                ExecuteStandUp();
//                return;
//            }

            //如果桌内有五个玩家，只要机器人大于等于1，那么站起一个机器人
            //注释 by gx 20181009 因为只打一局就散桌，所以不强制5个人时发生离桌行为
//            if( (realUserCnt + robotUserList.size()) >= 5 && robotUserList.size() >= 1){
//                if(robotUserList.get(0).equals(userId)){ //如果需要站起机器人的ID 与 当前机器人ID 相同，那么站起当前机器人
//                    logger.warn("站起机器人，桌子上达到5个玩家，并大于1个机器人的条件，{}, realUserCnt->{}, robotUserCnt->{}", toStringUserInfo(), realUserCnt, robotUserList.size());
//                    ExecuteStandUp();
//                    return;
//                }
//            }
        }
    }

    private String toStringUserInfo() {
        return ",CURRENT_CLIENT_USER:::userId->" + userId + ", nickName->" + nickName + ", roomId->" + roomId + ", tableId->" + tableId + ", gameOrderId->" + gameOrderId;
    }

    private void toStringPlayersScoreStore(String gameOrderId) {
        for (PlayerInfo playerInfo : onTablePlayers.values()) {
            logger.debug("牌局结束， gameOrderId->{}, userId->{}, nickName->{}, playerScoreStore->{}", gameOrderId, playerInfo.getUserId(), playerInfo.getNickName(), playerInfo.getPlayScoreStore());
        }
    }
}
